#include <string>
#include <iostream>
#include <vector>
#include <sstream>
#include <exception>

#include <Poco/Net/SocketAddress.h>
#include <Poco/Net/DialogSocket.h>
#include <Poco/Net/NetException.h>
#include <Poco/Exception.h>
#include <Poco/Thread.h>
#include <Poco/Net/SocketStream.h>
#include <Poco/FileStream.h>
#include <Poco/StreamCopier.h>
#include <Poco/Net/StreamSocket.h>
#include <Poco/Net/DNS.h>

#include "master.h"
#include "command/network/joincommand.h"
#include "command/network/runcommand.h"
#include "command/network/leavecommand.h"
#include "command/network/putcommand.h"
#include "command/network/getcommand.h"
#include "command/network/downloadthread.h"
#include "command/network/uploadthread.h"
#include "filemanager/filemanager.h"

using Poco::Net::SocketAddress;
using Poco::Net::DialogSocket;
using Poco::IOException;
using Poco::IllegalStateException;
using Poco::Thread;
using Poco::Net::SocketStream;
using Poco::FileInputStream;
using Poco::StreamCopier;
using Poco::Net::StreamSocket;
using Poco::Net::DNS;
using std::string;
using std::cout;
using std::endl;
using std::vector;
using std::istringstream;
using std::exception;
using Commands::JoinCommand;
using Commands::RunCommand;
using Commands::LeaveCommand;
using Commands::PutCommand;
using Commands::GetCommand;
using Commands::DownloadThread;
using Commands::UploadThread;

Master::Master() :
	fSocket(0),
	fCommFac(new CommandFactory())
{
	cout << "Master :: Creating Master" << endl;
	//cout << DNS::hostName() << endl;
	//cout << DNS::thisHost().addresses().front().toString() << endl;
}

Master::~Master() {
	cout << "Master :: Going to destroy Master" << endl;
	leave();
	cout << "Master :: Master destroyed" << endl;
}

void Master::connect(string const& host, int port) {
	// disconnect when we are already connected
	disconnect();
	cout << "Master :: Master trying to connect to " << host << " at " << port << endl;
	fSocket = new DialogSocket(SocketAddress(host, port));
}

string Master::communicate(string const& msg) {
	if(!fSocket) {
		throw IOException("Master :: Not connected. Connect before sending.");	
	}	
	cout << "Master :: Sending message: " << msg << endl;
	fSocket->sendMessage(msg);
	string response = "";
	fSocket->receiveMessage(response);
	cout << "Master :: Received response: " << response << endl;
	return response;
}

string Master::getLocalHostname() const {
	if (!fSocket) {
		throw IllegalStateException(
				"Master :: Not connected. Connect before getting the local host name.");
	}
	return fSocket->address().host().toString();
}

string Master::getConnectedHostname() const {
	if (!fSocket) {
		throw IllegalStateException(
				"Master :: Not connected. Connect before getting the remote host name.");
	}
	return fSocket->peerAddress().host().toString();
}

void Master::disconnect() {
	// Remove the socket
	if (fSocket) {
		cout << "Master :: Master leaving." << endl;
		fSocket->close();
		delete fSocket;
		fSocket = 0;
		cout << "Master :: Socket closed & deleted." << endl;
	}
}

void Master::leave()
{
	LeaveCommand byebye;
	cout << "Master :: Going to Leave " << getConnectedHostname() << endl;
	executeCommand(&byebye);
	cout << "Master :: Left slave" << endl;
	disconnect();
	
}

void Master::join(string const& host, int port)
{
	Command* join = new JoinCommand();
	connect(host, port);
	executeCommand(join);

	delete join;
}


void Master::put( string const& fileName )
{
	FileManager* fmgr = new FileManager();
	File f;
	try
	{
		f = fmgr->searchFile(fileName);
	} catch (FileNotFoundException e) {
		cout << "Master :: Put :: Filemanager :: File not found!" << endl;
		return;
	}

	cout << "Master :: Put :: Creating PutCommand " << endl;
	Command* upload = new PutCommand( fileName );
	executeCommand(upload);
	
	cout << "Master :: Put :: Waiting for port " << endl;
	string r = upload->result();
	istringstream ss(r);
	UInt16 port; ss >> port;

	cout << "Master :: Put :: Got port " << port <<  ", sending file " << endl;
	Thread thread("uploader");
	UploadThread putty(getConnectedHostname(), port, f.path());
	thread.start(putty);
	thread.join();

	cout << "Master :: Put :: File sent " << endl;

	delete upload;
}



bool Master::get(string const& fileName, File file)
{
	cout << "Master :: Get Method " << endl;
	Thread* thread = new Thread("downloader");
	DownloadThread getty(file.path());
	thread->start(getty);
	cout << "Master :: DownloadThread started" << endl;
	GetCommand download( fileName, getty.port() );
	cout << "Master :: GetCommand created" << endl;
	executeCommand(&download);
	cout << "Master :: GetCommand executed" << endl;
	string result = download.result();
	bool found = result == "OK";
	if (!found)
	{
		cout << "Master :: File transfer Not OK, removing file" << endl;
		if (file.exists())
			file.remove();
	} else {
		thread->join();
		cout << "Master :: Downloadthread completed" << endl;
		cout << "Master :: File transfer OK, adding to FileManager" << endl;
		FileManager fmgr;
		fmgr.addFile(fileName, file);
		cout << "Master :: File added to FileManager" << endl;
	}
	delete thread;
	return found;
}


void Master::command(string const& cmdline)
{
	cout << "Master :: Going to execute a command" << endl;
	Command* shell = new RunCommand(cmdline);
	executeCommand(shell);

	delete shell;
}

void Master::executeCommand( Command* command )
{
	try
	{
		cout << "Master :: Executing Command :: " << command->repr() << endl;
		string response = communicate( command->repr() );
		cout << "Master :: Setting Result " << endl;
		command->setResult(response, fSocket->peerAddress());
		cout << "Master :: Checkup, Result: :: " << response << endl;
	} catch (IOException e)
	{
		LeaveCommand* begone = dynamic_cast<LeaveCommand*>(command);
		if (!begone)
			throw e;
	}
	// parse the response and put it in the response of the command (or let the command do it ;) ) --> This is needed
	// for the PutCommand!
}

void Master::run()
{
	cout << "Master :: Starting master thread" << endl;
}
